﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.MemoryMappedFiles;

namespace IISLogAnalyzer
{
    // 新增小时IP统计结构
    public class HourlyIpStat
    {
        public string Hour { get; set; } = "";
        public List<string> Ips { get; set; } = new List<string>();
    }

    public class SortedAnalyzeResults
    {
        public IEnumerable<KeyValuePair<string, int>> domains { get; set; } = Enumerable.Empty<KeyValuePair<string, int>>();
        public IEnumerable<KeyValuePair<string, int>> ips { get; set; } = Enumerable.Empty<KeyValuePair<string, int>>();
        public IEnumerable<KeyValuePair<string, int>> staticFiles { get; set; } = Enumerable.Empty<KeyValuePair<string, int>>();
        public IEnumerable<KeyValuePair<string, int>> pages { get; set; } = Enumerable.Empty<KeyValuePair<string, int>>();
        public IEnumerable<KeyValuePair<string, int>> referers { get; set; } = Enumerable.Empty<KeyValuePair<string, int>>();
        public IEnumerable<KeyValuePair<string, int>> spiders { get; set; } = Enumerable.Empty<KeyValuePair<string, int>>();
        public IEnumerable<KeyValuePair<string, int>> userAgents { get; set; } = Enumerable.Empty<KeyValuePair<string, int>>();
        public IEnumerable<KeyValuePair<string, long>> fileFlows { get; set; } = Enumerable.Empty<KeyValuePair<string, long>>();
        public IEnumerable<KeyValuePair<string, int>> dataFolders { get; set; } = Enumerable.Empty<KeyValuePair<string, int>>();
        public IEnumerable<KeyValuePair<string, long>> dataFoldersFlow { get; set; } = Enumerable.Empty<KeyValuePair<string, long>>();
        public IEnumerable<KeyValuePair<string, int>> statusCodes { get; set; } = Enumerable.Empty<KeyValuePair<string, int>>();
        public IEnumerable<KeyValuePair<string, int>> timeDistribution { get; set; } = Enumerable.Empty<KeyValuePair<string, int>>();
        public IEnumerable<KeyValuePair<string, int>> osDistribution { get; set; } = Enumerable.Empty<KeyValuePair<string, int>>();
        public IEnumerable<KeyValuePair<string, int>> browserDistribution { get; set; } = Enumerable.Empty<KeyValuePair<string, int>>();
        public IEnumerable<KeyValuePair<string, int>> countryDistribution { get; set; } = Enumerable.Empty<KeyValuePair<string, int>>();

        // 新增小时唯一IP分布
        public List<HourlyIpStat> hourlyIpDistribution { get; set; } = new List<HourlyIpStat>();
    }

    public class AnalyzeResults
    {
        public ConcurrentDictionary<string, int> DomainRank = new();
        public ConcurrentDictionary<string, int> IpRank = new();
        public ConcurrentDictionary<string, int> StaticFileRank = new();
        public ConcurrentDictionary<string, int> PageUrlRank = new();
        public ConcurrentDictionary<string, int> RefererRank = new();
        public ConcurrentDictionary<string, int> SpiderFromRank = new();
        public ConcurrentDictionary<string, int> UserAgentRank = new();
        public ConcurrentDictionary<string, long> FileFlowStat = new();
        public ConcurrentDictionary<string, int> DataPathFolderRank = new();
        public ConcurrentDictionary<string, long> DataPathFolderFlowStat = new();
        public ConcurrentDictionary<string, int> StatusCodeRank = new();
        public ConcurrentDictionary<string, int> TimeDistributionRank = new();
        public ConcurrentDictionary<string, int> OsDistributionRank = new();
        public ConcurrentDictionary<string, int> BrowserDistributionRank = new();
        public ConcurrentDictionary<string, int> CountryDistributionRank = new();

        // 新增小时唯一IP统计
        public ConcurrentDictionary<string, HashSet<string>> HourIpSet = new();
    }

    public class SpiderInfo
    {
        public int Id { get; set; }
        public string Key { get; set; }
        public string Name { get; set; }
        public string Alias { get; set; }
        public string From { get; set; }
    }

    public static class LogAnalyzer
    {
        static readonly string[] StaticExtensions = {
            ".js", ".css", ".png", ".jpg", ".gif", ".ico", ".svg", ".woff", ".ttf", ".eot", ".mp4", ".webp", ".pdf", ".zip", ".rar", ".7z", ".exe"
        };
        static readonly string[] PageExtensions = { ".aspx", ".php", ".jsp", ".html", ".htm" };

        public static List<SpiderInfo> Spiders = new()
        {
            new SpiderInfo{Id=1, Key="baiduspider", Name="百度蜘蛛", Alias="百度蜘蛛", From="baidu"},
            new SpiderInfo{Id=2, Key="googlebot", Name="谷歌蜘蛛", Alias="谷歌蜘蛛", From="google"},
            new SpiderInfo{Id=3, Key="sogou", Name="搜狗蜘蛛", Alias="搜狗蜘蛛", From="sougou"},
            new SpiderInfo{Id=4, Key="360spider", Name="360蜘蛛", Alias="360蜘蛛", From="so360"},
            new SpiderInfo{Id=5, Key="bingbot", Name="微软bing", Alias="其他蜘蛛", From="other"},
            new SpiderInfo{Id=6, Key="slurp", Name="雅虎蜘蛛", Alias="其他蜘蛛", From="other"},
            new SpiderInfo{Id=7, Key="sosospider", Name="搜搜", Alias="其他蜘蛛", From="other"},
            new SpiderInfo{Id=8, Key="youdaobot", Name="有道", Alias="其他蜘蛛", From="other"},
            new SpiderInfo{Id=9, Key="etaospider", Name="一淘网蜘蛛", Alias="其他蜘蛛", From="other"},
            new SpiderInfo{Id=10, Key="jikespider", Name="即刻蜘蛛", Alias="其他蜘蛛", From="other"},
            new SpiderInfo{Id=11, Key="easouspider", Name="宜搜蜘蛛", Alias="其他蜘蛛", From="other"},
            new SpiderInfo{Id=12, Key="yisouspider", Name="一搜蜘蛛", Alias="其他蜘蛛", From="other"},
            new SpiderInfo{Id=13, Key="ahrefsbot", Name="Ahrefs蜘蛛", Alias="其他蜘蛛", From="other"},
            new SpiderInfo{Id=14, Key="docomo", Name="谷歌日本", Alias="其他蜘蛛", From="other"},
            new SpiderInfo{Id=15, Key="yandexbot", Name="俄罗斯蜘蛛", Alias="其他蜘蛛", From="other"},
            new SpiderInfo{Id=16, Key="ia_archiver", Name="Alexa蜘蛛", Alias="其他蜘蛛", From="other"},
            new SpiderInfo{Id=17, Key="msnbot", Name="MSN爬虫", Alias="其他蜘蛛", From="other"}
        };

        public static AnalyzeResults AnalyzeLogFileParallel_MMF(string filePath, int chunkSize, string? targetHost = null)
        {
            filePath = Path.GetFullPath(filePath);

            string[]? fields = null;
            long dataStartPos = 0;
            using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            using (var reader = new StreamReader(fs, Encoding.UTF8, true, 4096, true))
            {
                string? line;
                while ((line = reader.ReadLine()) != null)
                {
                    if (line.StartsWith("#Fields:"))
                    {
                        fields = line.Substring(8).Trim().Split(' ');
                        dataStartPos = fs.Position;
                        break;
                    }
                }
            }
            if (fields == null) throw new Exception("未找到#Fields定义行！");
            if (!fields.Contains("cs-uri-stem")) throw new Exception("日志缺少cs-uri-stem字段！");
            if (!fields.Contains("c-ip")) throw new Exception("日志缺少c-ip字段！");

            int scBytesIdx = Array.IndexOf(fields, "sc-bytes");
            bool hasScBytes = scBytesIdx >= 0;
            int hostIdx = Array.IndexOf(fields, "cs-host");
            int scStatusIdx = Array.IndexOf(fields, "sc-status");
            int dateIdx = Array.IndexOf(fields, "date");
            int timeIdx = Array.IndexOf(fields, "time");

            // 2. 计算分块（按字节）
            long fileLength = new FileInfo(filePath).Length;
            int chunkCount = (int)Math.Ceiling((fileLength - dataStartPos) / (double)chunkSize);
            var chunks = new List<(long Offset, int Length, int Index)>(chunkCount);
            for (int i = 0; i < chunkCount; i++)
            {
                long offset = dataStartPos + i * (long)chunkSize;
                int length = (int)Math.Min(chunkSize, fileLength - offset);
                chunks.Add((offset, length, i));
            }

            // 3. 全局并发聚合字典
            var allDomainRank = new ConcurrentDictionary<string, int>();
            var allIpRank = new ConcurrentDictionary<string, int>();
            var allStaticFileRank = new ConcurrentDictionary<string, int>();
            var allPageUrlRank = new ConcurrentDictionary<string, int>();
            var allRefererRank = new ConcurrentDictionary<string, int>();
            var allSpiderFromRank = new ConcurrentDictionary<string, int>();
            var allUserAgentRank = new ConcurrentDictionary<string, int>();
            var allFileFlowStat = new ConcurrentDictionary<string, long>();
            var allDataPathFolderRank = new ConcurrentDictionary<string, int>();
            var allDataPathFolderFlowStat = new ConcurrentDictionary<string, long>();
            var allStatusCodeRank = new ConcurrentDictionary<string, int>();
            var allTimeDistributionRank = new ConcurrentDictionary<string, int>();
            var allOsDistributionRank = new ConcurrentDictionary<string, int>();
            var allBrowserDistributionRank = new ConcurrentDictionary<string, int>();
            var allCountryDistributionRank = new ConcurrentDictionary<string, int>();

            // 新增小时唯一IP统计
            var allHourIpSet = new ConcurrentDictionary<string, HashSet<string>>();

            using var mmf = MemoryMappedFile.CreateFromFile(filePath, FileMode.Open, null, 0, MemoryMappedFileAccess.Read);

            Parallel.ForEach(chunks,
                () => (
                    new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase),
                    new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase),
                    new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase),
                    new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase),
                    new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase),
                    new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase),
                    new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase),
                    new Dictionary<string, long>(StringComparer.OrdinalIgnoreCase),
                    new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase),
                    new Dictionary<string, long>(StringComparer.OrdinalIgnoreCase),
                    new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase),
                    new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase),
                    new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase),
                    new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase),
                    new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase),
                    new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase) // 小时IP统计
                ),
                (chunk, state, local) =>
                {
                    var (
                        domainRank, ipRank, staticFileRank, pageUrlRank, refererRank, spiderFromRank,
                        userAgentRank, fileFlowStat, dataPathFolderRank, dataPathFolderFlowStat,
                        statusCodeRank, timeDistributionRank, osDistributionRank, browserDistributionRank, countryDistributionRank, hourIpSet
                    ) = local;

                    using var accessor = mmf.CreateViewAccessor(chunk.Offset, chunk.Length, MemoryMappedFileAccess.Read);
                    byte[] buffer = new byte[chunk.Length];
                    accessor.ReadArray(0, buffer, 0, chunk.Length);

                    var span = buffer.AsSpan();

                    int start = 0;
                    if (chunk.Offset != dataStartPos)
                    {
                        int lineEnd = span.IndexOf((byte)'\n');
                        start = (lineEnd >= 0) ? lineEnd + 1 : 0;
                    }

                    while (start < span.Length)
                    {
                        int lineEnd = span.Slice(start).IndexOf((byte)'\n');
                        if (lineEnd < 0) lineEnd = span.Length - start;
                        var lineSpan = span.Slice(start, lineEnd).TrimEnd((byte)'\r');
                        start += lineEnd + 1;

                        if (lineSpan.Length == 0) continue;
                        if (lineSpan[0] == (byte)'#') continue;

                        string lineStr = System.Text.Encoding.UTF8.GetString(lineSpan);
                        var arr = SplitLogLine(lineStr);
                        if (arr.Length != fields.Length) continue;

                        string host = GetField(arr, fields, "cs-host");
                        if (!string.IsNullOrEmpty(targetHost) && !string.Equals(host, targetHost, StringComparison.OrdinalIgnoreCase))
                            continue;

                        string cip = GetField(arr, fields, "c-ip");
                        string stem = GetField(arr, fields, "cs-uri-stem");
                        string referer = GetField(arr, fields, "cs(Referer)");
                        string ua = GetField(arr, fields, "cs(User-Agent)");
                        string scStatus = scStatusIdx >= 0 ? arr[scStatusIdx] : "";
                        string date = dateIdx >= 0 ? arr[dateIdx] : "";
                        string time = timeIdx >= 0 ? arr[timeIdx] : "";
                        string os = GetOS(ua);
                        string browser = GetBrowser(ua);
                        string country = GetCountry(cip, ua);

                        if (!string.IsNullOrEmpty(host))
                            AddOrIncr(domainRank, host);

                        if (!string.IsNullOrEmpty(cip))
                            AddOrIncr(ipRank, cip);

                        var ext = Path.GetExtension(stem).ToLowerInvariant();
                        if (StaticExtensions.Contains(ext) && !string.IsNullOrEmpty(stem))
                            AddOrIncr(staticFileRank, stem);

                        bool isPage = false;
                        if (!string.IsNullOrEmpty(stem))
                        {
                            if (PageExtensions.Contains(ext))
                                isPage = true;
                            else if (!stem.Contains('.'))
                                isPage = true;
                        }
                        if (isPage)
                            AddOrIncr(pageUrlRank, stem);

                        if (!string.IsNullOrEmpty(referer) && referer != "-")
                            AddOrIncr(refererRank, referer);

                        if (!string.IsNullOrEmpty(ua))
                            AddOrIncr(userAgentRank, ua);

                        var spider = GetSpider(ua);
                        if (!string.IsNullOrEmpty(spider.From))
                            AddOrIncr(spiderFromRank, spider.From);

                        if (hasScBytes && !string.IsNullOrEmpty(stem))
                        {
                            if (long.TryParse(arr[scBytesIdx], out var bytes))
                                AddOrIncr(fileFlowStat, stem, bytes);
                        }

                        // 文件夹访问量和流量
                        if (!string.IsNullOrEmpty(stem))
                        {
                            var idx = stem.IndexOf("/Data/", StringComparison.OrdinalIgnoreCase);
                            if (idx >= 0)
                            {
                                string sub = stem.Substring(idx + 6);
                                var slashIdx = sub.IndexOf('/');
                                string folder = (slashIdx >= 0) ? sub.Substring(0, slashIdx) : sub;
                                if (!string.IsNullOrEmpty(folder))
                                {
                                    AddOrIncr(dataPathFolderRank, folder);
                                    // 流量统计
                                    if (hasScBytes && long.TryParse(arr[scBytesIdx], out var bytes))
                                        AddOrIncr(dataPathFolderFlowStat, folder, bytes);
                                }
                            }
                        }

                        if (!string.IsNullOrEmpty(scStatus))
                            AddOrIncr(statusCodeRank, scStatus);

                        if (!string.IsNullOrEmpty(date) && !string.IsNullOrEmpty(time))
                        {
                            string hourKey = $"{date} {time.Substring(0, 2)}";
                            AddOrIncr(timeDistributionRank, $"{date} {time}");

                            // 每小时唯一IP统计
                            if (!string.IsNullOrEmpty(cip))
                            {
                                if (!hourIpSet.TryGetValue(hourKey, out var ipSet))
                                {
                                    ipSet = new HashSet<string>();
                                    hourIpSet[hourKey] = ipSet;
                                }
                                ipSet.Add(cip);
                            }
                        }

                        if (!string.IsNullOrEmpty(os))
                            AddOrIncr(osDistributionRank, os);

                        if (!string.IsNullOrEmpty(browser))
                            AddOrIncr(browserDistributionRank, browser);

                        if (!string.IsNullOrEmpty(country))
                            AddOrIncr(countryDistributionRank, country);
                    }
                    return local;
                },
                local =>
                {
                    var (
                        domainRank, ipRank, staticFileRank, pageUrlRank, refererRank, spiderFromRank,
                        userAgentRank, fileFlowStat, dataPathFolderRank, dataPathFolderFlowStat,
                        statusCodeRank, timeDistributionRank, osDistributionRank, browserDistributionRank, countryDistributionRank, hourIpSet
                    ) = local;
                    foreach (var kv in domainRank) allDomainRank.AddOrUpdate(kv.Key, kv.Value, (k, v) => v + kv.Value);
                    foreach (var kv in ipRank) allIpRank.AddOrUpdate(kv.Key, kv.Value, (k, v) => v + kv.Value);
                    foreach (var kv in staticFileRank) allStaticFileRank.AddOrUpdate(kv.Key, kv.Value, (k, v) => v + kv.Value);
                    foreach (var kv in pageUrlRank) allPageUrlRank.AddOrUpdate(kv.Key, kv.Value, (k, v) => v + kv.Value);
                    foreach (var kv in refererRank) allRefererRank.AddOrUpdate(kv.Key, kv.Value, (k, v) => v + kv.Value);
                    foreach (var kv in spiderFromRank) allSpiderFromRank.AddOrUpdate(kv.Key, kv.Value, (k, v) => v + kv.Value);
                    foreach (var kv in userAgentRank) allUserAgentRank.AddOrUpdate(kv.Key, kv.Value, (k, v) => v + kv.Value);
                    foreach (var kv in fileFlowStat) allFileFlowStat.AddOrUpdate(kv.Key, kv.Value, (k, v) => v + kv.Value);
                    foreach (var kv in dataPathFolderRank) allDataPathFolderRank.AddOrUpdate(kv.Key, kv.Value, (k, v) => v + kv.Value);
                    foreach (var kv in dataPathFolderFlowStat) allDataPathFolderFlowStat.AddOrUpdate(kv.Key, kv.Value, (k, v) => v + kv.Value);
                    foreach (var kv in statusCodeRank) allStatusCodeRank.AddOrUpdate(kv.Key, kv.Value, (k, v) => v + kv.Value);
                    foreach (var kv in timeDistributionRank) allTimeDistributionRank.AddOrUpdate(kv.Key, kv.Value, (k, v) => v + kv.Value);
                    foreach (var kv in osDistributionRank) allOsDistributionRank.AddOrUpdate(kv.Key, kv.Value, (k, v) => v + kv.Value);
                    foreach (var kv in browserDistributionRank) allBrowserDistributionRank.AddOrUpdate(kv.Key, kv.Value, (k, v) => v + kv.Value);
                    foreach (var kv in countryDistributionRank) allCountryDistributionRank.AddOrUpdate(kv.Key, kv.Value, (k, v) => v + kv.Value);

                    // 合并小时唯一IP统计
                    foreach (var kv in hourIpSet)
                    {
                        allHourIpSet.AddOrUpdate(kv.Key, kv.Value, (k, v) =>
                        {
                            v.UnionWith(kv.Value);
                            return v;
                        });
                    }
                }
            );

            return new AnalyzeResults
            {
                DomainRank = allDomainRank,
                IpRank = allIpRank,
                StaticFileRank = allStaticFileRank,
                PageUrlRank = allPageUrlRank,
                RefererRank = allRefererRank,
                SpiderFromRank = allSpiderFromRank,
                UserAgentRank = allUserAgentRank,
                FileFlowStat = allFileFlowStat,
                DataPathFolderRank = allDataPathFolderRank,
                DataPathFolderFlowStat = allDataPathFolderFlowStat,
                StatusCodeRank = allStatusCodeRank,
                TimeDistributionRank = allTimeDistributionRank,
                OsDistributionRank = allOsDistributionRank,
                BrowserDistributionRank = allBrowserDistributionRank,
                CountryDistributionRank = allCountryDistributionRank,
                HourIpSet = allHourIpSet // 新增
            };
        }

        public static SortedAnalyzeResults ToSortedAnalyzeResults(AnalyzeResults results)
        {
            var sortedResult = new SortedAnalyzeResults
            {
                domains = results.DomainRank.OrderByDescending(x => x.Value),
                ips = results.IpRank.OrderByDescending(x => x.Value),
                staticFiles = results.StaticFileRank.OrderByDescending(x => x.Value),
                pages = results.PageUrlRank.OrderByDescending(x => x.Value),
                referers = results.RefererRank.OrderByDescending(x => x.Value),
                spiders = results.SpiderFromRank.OrderByDescending(x => x.Value),
                userAgents = results.UserAgentRank.OrderByDescending(x => x.Value),
                fileFlows = results.FileFlowStat.OrderByDescending(x => x.Value),
                dataFolders = results.DataPathFolderRank.OrderByDescending(x => x.Value),
                dataFoldersFlow = results.DataPathFolderFlowStat.OrderByDescending(x => x.Value).Select(x => new KeyValuePair<string, long>(x.Key, x.Value / 1024 / 1024)),
                statusCodes = results.StatusCodeRank.OrderByDescending(x => x.Value),
                timeDistribution = results.TimeDistributionRank.OrderByDescending(x => x.Key),
                osDistribution = results.OsDistributionRank.OrderByDescending(x => x.Value),
                browserDistribution = results.BrowserDistributionRank.OrderByDescending(x => x.Value),
                countryDistribution = results.CountryDistributionRank.OrderByDescending(x => x.Value)
            };

            // 新增小时唯一IP分布
            var hourlyIpList = results.HourIpSet
                .OrderBy(x => x.Key)
                .Select(x => new HourlyIpStat
                {
                    Hour = x.Key,
                    Ips = x.Value.ToList()
                }).ToList();

            sortedResult.hourlyIpDistribution = hourlyIpList;

            return sortedResult;
        }

        private static void AddOrIncr(Dictionary<string, int> dic, string key, int incr = 1)
        {
            if (dic.TryGetValue(key, out int v))
                dic[key] = v + incr;
            else
                dic[key] = incr;
        }
        private static void AddOrIncr(Dictionary<string, long> dic, string key, long incr)
        {
            if (dic.TryGetValue(key, out long v))
                dic[key] = v + incr;
            else
                dic[key] = incr;
        }

        static string[] SplitLogLine(string l)
        {
            return l.Split(' ');
        }

        static string GetField(string[] arr, string[] fields, string fieldName)
        {
            int idx = Array.IndexOf(fields, fieldName);
            if (idx == -1 || idx >= arr.Length) return "";
            return arr[idx];
        }

        static SpiderInfo GetSpider(string ua)
        {
            ua = (ua ?? "").ToLower();
            foreach (var s in Spiders)
                if (ua.Contains(s.Key)) return s;
            return new SpiderInfo { Id = 100, Key = "", Name = "其他流量", Alias = "其他流量", From = "other_traffic" };
        }

        // 操作系统识别（简化示例，可扩展）
        static string GetOS(string ua)
        {
            if (string.IsNullOrEmpty(ua)) return "";
            ua = ua.ToLower();
            if (ua.Contains("windows")) return "Windows";
            if (ua.Contains("linux")) return "Linux";
            if (ua.Contains("mac")) return "MacOS";
            if (ua.Contains("android")) return "Android";
            if (ua.Contains("iphone") || ua.Contains("ios")) return "iOS";
            return "Other";
        }

        // 浏览器识别（简化）
        static string GetBrowser(string ua)
        {
            if (string.IsNullOrEmpty(ua)) return "";
            ua = ua.ToLower();
            if (ua.Contains("chrome")) return "Chrome";
            if (ua.Contains("firefox")) return "Firefox";
            if (ua.Contains("safari") && !ua.Contains("chrome")) return "Safari";
            if (ua.Contains("edge")) return "Edge";
            if (ua.Contains("msie") || ua.Contains("trident")) return "IE";
            return "Other";
        }

        // 国家识别（IP段可接入第三方库，这里仅占位）
        static string GetCountry(string ip, string ua)
        {
            // 1. 先通过IP获取国家（可接入第三方库）
            // TODO: 可用IP地理库，如IP2Location、纯真等
            string country = "";

            // 2. 如果IP无法获取，则尝试从User-Agent中获取语言国家
            if (string.IsNullOrEmpty(country))
            {
                // 常见格式：User-Agent 里 Accept-Language 字段通常在请求头，但部分UA有类似en-US、zh-CN等
                // 简易正则提取如 zh-CN/en-US/fr-FR 等
                var match = System.Text.RegularExpressions.Regex.Match(ua, @"([a-z]{2}-[A-Z]{2})");
                if (match.Success)
                {
                    country = match.Groups[1].Value;
                }
            }

            return country;
        }
    }

    public static class LanguageCountryMapper
    {
        private static readonly Dictionary<string, string> LangCountryMap = new Dictionary<string, string>
    {
        {"zh-CN", "中国"},
        {"zh-TW", "台湾"},
        {"en-US", "美国"},
        {"en-GB", "英国"},
        {"en-CA", "加拿大"},
        {"de-DE", "德国"},
        {"de-AT", "奥地利"},
        {"fr-FR", "法国"},
        {"es-ES", "西班牙"},
        {"es-AR", "阿根廷"},
        {"ns-CN", "中国（自定义编码）"},
        {"dn-SC", "塞舌尔（自定义编码）"},
    };

        /// <summary>
        /// 根据语言编码获取国家中文名
        /// </summary>
        public static string GetCountryName(string langCode)
        {
            if (string.IsNullOrWhiteSpace(langCode))
                return "未知";
            if (LangCountryMap.TryGetValue(langCode, out var country))
                return country;
            return langCode; // 未知编码直接返回原始编码，也可以返回“未知”
        }
    }
}